1 /**
  2 	gn_tracking uses an object literal notation: configuration objects sit snugly inside it, 
  3 	alongside the various functions it exposes. 
  4 
  5 	In reality, init() is the only function you should need to access directly 
  6 
  7 	@namespace gn_tracking
  8 */
  9 var gn_tracking = {
 10 
 11 	/** Internal config object; holds a Google object or string, a quantcast CSV string, and a ComScore object */
 12 	config: {
 13 		google:		{
 14 				urchin: '',
 15 				ga:		''
 16 		},
 17 		google:		'',
 18 		quantcast:	'',
 19 		comscore:	''		
 20 	},
 21 
 22 	/** Internal flag store;, used mostly to kill successive calls to init */
 23 	flags: { loaded: false },
 24 
 25 	/** Accepts a config param, and then for each item in the config object, calls its matching function
 26 		@param {Object} config The config object; see the internal ga_tracking.config object for a reference to its structure
 27 	*/
 28 	init: function(config) {
 29 		var that = this;
 30 
 31 		// If our init flag has been triggered, quit now
 32 		if (typeof this.flags.initialised != 'undefined' && this.flags.initialised) {
 33 			return false;
 34 		// Else, set the flag and continue
 35 		} else {
 36 			this.flags.initialised = true;
 37 		}
 38 
 39 		// Overwrite our config object with the passed version
 40 		if (config) this.config = config;
 41 
 42 		// For each element in the config object, inspect gn_tracking for a function with a matching name and trigger it
 43 		for (var prop in this.config) {
 44 			if (!this.is_empty(this.config[prop]) && typeof this[prop] == 'function') {
 45 				this[prop]();
 46 			}
 47 		}
 48 	},
 49 
 50 	/** Loads the required GA script, and queues a callback to be fired when the script loads */
 51 	google: function() {
 52 		var that = this;
 53 
 54 		// Init our google tracking flags
 55 		this.flags.google = {};
 56 
 57 		// If we have a CSV of GA tags...
 58 		if (typeof(this.config.google) == 'string') {
 59 			var ids = this.config.google.split(',');
 60 
 61 			// Split it, and populate the google config object
 62 			this.config.google = {};
 63 			if (ids[0]) this.config.google.ga		 = ids[0];
 64 			if (ids[1]) this.config.google.urchin	 = ids[1];
 65 		}
 66 		
 67 		// If we have a GA 2.0 api request, call the script, and queue the callback
 68 		if (this.config.google.ga) {
 69 			this.add_script(
 70 				'http://www.google-analytics.com/ga.js',
 71 				function() {
 72 
 73 					// The presense of pageTracker means that this page is already using the GA 2.0 APi; abort
 74 					if (typeof pageTracker != 'undefined' && !that.is_empty(pageTracker)) return false;
 75 
 76 					// GA payload
 77 					pageTracker = _gat._getTracker(that.config.google.ga);
 78 					pageTracker._initData();
 79 					pageTracker._trackPageview();
 80 					
 81 					// Update gn_tracking with our success flag
 82 					that.flags.google.ga = true;
 83 				}
 84 			);
 85 		}
 86 	
 87 		// If we have a GA 1.0 api request, call the script, and queue the callback
 88 		if (this.config.google.urchin) {
 89 			this.add_script(
 90 				'http://www.google-analytics.com/urchin.js',
 91 				function() {
 92 
 93 					// The presense of _uacct means that this page is already using the GA 1.0 APi; abort
 94 					if (typeof _uacct != 'undefined' && !that.is_empty(_uacct)) return false;
 95 
 96 					// GA payload
 97 					_uacct = that.config.google.urchin;
 98 					urchinTracker();
 99 					
100 					// Update gn_tracking with our success flag
101 					that.flags.google.urchin = true;
102 				}
103 			);
104 		}
105 	},
106 
107 
108 	/** Loads the required quantcast script, and queues a callback to be fired when the script loads */
109 	quantcast: function() {
110 		var that = this;
111 
112 		this.add_script(
113 			"http://edge.quantserve.com/quant.js",
114 			function() {
115 
116 				// The presense of _qacct or _qoptions means that this page is already using quantcast tags; abort
117 				if (typeof _qacct != 'undefined' && !that.is_empty(_qacct)) return false;
118 				if (typeof _qoptions != 'undefined' && !that.is_empty(_qoptions)) return false;
119 
120 				// Split our CSV config string and call Quantserve() with each value
121 				var ids = that.config.quantcast.split(',');
122 				for (var i = 0; i < ids.length; i++) {
123 					_qoptions={qacct: ids[i]};
124 					quantserve();
125 				}
126 
127 				// Update gn_tracking with our success flag
128 				that.flags.quantcast = true;
129 			}
130 		);
131 	},
132 
133 
134 	/** Loads the required ComScore script, and queues a callback to be fired when the script loads */
135 	comscore: function() {
136 		var that = this;
137 
138 		this.add_script(
139 			(document.location.protocol == 'https:' ? 'https://sb' : 'http://b')+".scorecardresearch.com/beacon.js",
140 			function() {
141 				// Standard Comscore payload
142 				COMSCORE.beacon(that.config.comscore);
143 
144 				// Update gn_tracking with our success flag
145 				that.flags.comscore = true;
146 			}
147 		);
148 	},
149 
150 
151 	/** Aaah, the big deal here, add_script asyncronously adds a script object to the head, monitors its load status, 
152 		and then queues a callback to be executed when it's complete
153 		@param {String}		url			Script to be attached to the page
154 		@param {Function}	callback	Function to be executed when the script is fully loaded
155 	*/
156 	add_script: function(url, callback) {
157 		var that = this;
158 
159 
160 		var script = document.createElement("script");
161 		var head = document.getElementsByTagName("head")[0];
162 		script.src = url;
163 
164 		script.onload = script.onreadystatechange = function(){
165 			if (!this.readyState || this.readyState == "loaded" || this.readyState == "complete") {
166 				if (typeof callback == 'function') {
167 					callback();
168 				}
169 
170 				// Handle memory leak in IE
171 				script.onload = script.onreadystatechange = null;
172 				head.removeChild(script);
173 			}
174 		};
175 
176 		head.appendChild(script);
177 	},
178 
179 
180 	/**	Accepts a variable and attempts to determine if it's truly 'empty'. Mostly used as Object's length property lies 
181 		@param value Any primitive or basic datatype (string, number, boolan, array, object, etc.)
182 	*/
183 	is_empty: function(value) {
184 		switch (typeof(value)) {
185 			case 'object': 
186 				// If this object has any properties directly defined on it... well, it's not empty then, is it?
187 				for(var prop in value) {
188 					if (value.hasOwnProperty(prop)) return false;
189 				}
190 
191 				return true;
192 				break;
193 
194 			// Strings and arrays are easy, just count the values
195 			case 'string':
196 			case 'array':
197 				if (value.length > 0) {
198 					return false;
199 				} else {
200 					return true;
201 				}
202 
203 				break;
204 
205 			// Most other datatypes, by their existence, can't be empty; return false
206 			default: 
207 				return false;	
208 				break;
209 		}
210 	}
211 };
212